
/************************************************************************/
/* 
*/
/************************************************************************/

#define  _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <gbm.h>
#include <drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <libevdev/libevdev.h>
#include <linux/vt.h>
#include <fontconfig/fontconfig.h>
#include <sys/mman.h>



#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include "gui.h"

int							g_hDevice;
uint32_t					g_hCrtcId;
uint32_t					g_hConnectorId;
drmModeModeInfo g_stDisplayMode;
MainWindowPtr			g_focusWidget=NULL;
std::vector<libevdev*> g_ltInputDev;
int g_iMousePos[2] = { 0, 0 };
int g_szCursorClip[2] = { 0 };


void getCursorPos(int &x, int& y )
{
	x=g_iMousePos[0];
	y=g_iMousePos[1];
}

static void initKms( )
{
	static const char device_name[] = "/dev/dri/card0";
	g_hDevice = open(device_name, O_RDWR);
	if (g_hDevice < 0) 
	{
		/* Probably permissions error */
		fprintf(stderr, "couldn't open %s, skipping\n", device_name);
		exit(0);
		return;
	}

	drmModeRes *resources;
	resources = drmModeGetResources(g_hDevice);
	drmModeConnector *connector = NULL;
	for (int i = 0; i < resources->count_connectors; i++)
	{
		connector = drmModeGetConnector(g_hDevice, resources->connectors[i]);
		if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0)
		{
			drmModeEncoder *encoder;
			encoder = drmModeGetEncoder( g_hDevice, connector->encoders[connector->encoder_id] );
			for ( unsigned j = 0; j < resources->count_crtcs; ++j )
			{   
				if ( encoder->possible_crtcs & (1 << j) )
				{
					g_hCrtcId = resources->crtcs[j];
					g_hConnectorId = connector->connector_id;
					g_stDisplayMode = connector->modes[1];
					drmModeFreeConnector(connector);
					return;
				}
			}
		}

		drmModeFreeConnector(connector);
	}
}

struct DoubleBuffer
{
	DoubleBuffer() { ptrBufferObject = NULL; hKmsFb = 0; }

	gbm_bo	*ptrBufferObject;
	uint32_t	hKmsFb;
};

void swapBuffers( gbm_surface *gs  )
{
	static DoubleBuffer buffer[2];
	static int current = 0;

	buffer[current].ptrBufferObject = gbm_surface_lock_front_buffer(gs);

	uint32_t hKmsBuffer = gbm_bo_get_handle(buffer[current].ptrBufferObject).u32;
	uint32_t stride = gbm_bo_get_stride(buffer[current].ptrBufferObject);
	uint32_t width = g_stDisplayMode.hdisplay, height = g_stDisplayMode.vdisplay;
	int ret;
	ret = drmModeAddFB(g_hDevice, width, height, 24, 32, stride, hKmsBuffer, &buffer[current].hKmsFb);
	ret = drmModeSetCrtc(g_hDevice, g_hCrtcId, buffer[current].hKmsFb, 0, 0, &g_hConnectorId, 1, &g_stDisplayMode);
	ret = drmModePageFlip(g_hDevice, g_hCrtcId, buffer[current].hKmsFb, DRM_MODE_PAGE_FLIP_EVENT, NULL);

	/* Clear */
	current = 1-current;
	if ( buffer[current].ptrBufferObject !=NULL &&  buffer[current].hKmsFb !=0 )
	{
		drmModeRmFB(g_hDevice, buffer[current].hKmsFb);
		gbm_surface_release_buffer(gs, buffer[current].ptrBufferObject);
	}
}

void setFocus( MainWindow* w )
{
	g_focusWidget = w;
}

void getScreenSize( int& sw, int& sh )
{
	sh = g_stDisplayMode.vdisplay;
	sw = g_stDisplayMode.hdisplay;
}

void modeset_page_flip_event(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { }

void framesync()
{
	static long eclipsed = 0; // nsec
	static long T = 1000000000.0/60.0;

	timeval tv;
	timespec req, rem;
	gettimeofday(&tv, NULL);
	eclipsed += tv.tv_usec*1000.0;

	if (eclipsed>T)
	{
		eclipsed = 0.0;
	}

	req.tv_sec = 0;
	req.tv_nsec = T-eclipsed;
	nanosleep( &req, &rem );
}



void initInputDevice()
{
	for(int i=0; ; i++) {
		char devPath[100] = {0};
		sprintf(devPath, "/dev/input/event%d", i);
		int fd = open(devPath, O_RDONLY|O_NONBLOCK);
		if (fd<0) {
			break;
		}
		libevdev* dev=NULL;
		libevdev_new_from_fd(fd, &dev);
		if(dev)
			g_ltInputDev.push_back(dev);
		else
			break;
	}
}

bool getInputMsg( input_event& msg )
{
	for (int i=0; i<g_ltInputDev.size(); i++) {
		if ( libevdev_next_event(g_ltInputDev[i], LIBEVDEV_READ_FLAG_NORMAL, &msg) == 0 ) {
			return true;
		}
	}

	return false;
}

void mainloop( )
{
	fd_set fds;
	struct timeval v;
	drmEventContext ev;

	FD_ZERO(&fds);
	memset(&v, 0, sizeof(v));
	memset(&ev, 0, sizeof(ev));
	ev.version = DRM_EVENT_CONTEXT_VERSION;
	ev.page_flip_handler = modeset_page_flip_event;

	input_event msg;

	if( g_focusWidget == NULL )
	{
		printf("g_focusWidget==NULL Error !\n");
		exit(0);
	}

	// WM_NCCREATE
	g_focusWidget->onCreate();
	g_focusWidget->paintEvent();
	swapBuffers( (gbm_surface*)g_focusWidget->nativeWindow() );

	while (1) 
	{
		if( getInputMsg(msg) )
		{
			if( msg.type == EV_KEY && msg.code == KEY_ESC ) {
				break;
			}

			if ( g_focusWidget && msg.type == EV_KEY ) {
				g_focusWidget->keyEvent(msg.code, msg.value);
			}

			if ( msg.type == EV_REL &&( msg.code==REL_X|| msg.code==REL_Y)) {
				int pos = g_iMousePos[msg.code]+msg.value;
				g_iMousePos[msg.code] = (msg.value>0) ? min( g_szCursorClip[msg.code], pos ) : max( 0, pos );
				drmModeMoveCursor(g_hDevice, g_hCrtcId, g_iMousePos[0], g_iMousePos[1]);
			}
		}

		FD_SET(g_hDevice, &fds);

		int ret;
		ret = select( g_hDevice+1, &fds, NULL, NULL, &v);
		if (ret < 0) 
		{
			fprintf(stderr, "select() failed with %d: %m\n", errno);
			break;
		} 
		else if (FD_ISSET(g_hDevice, &fds))
		{
			drmHandleEvent(g_hDevice, &ev);
			framesync();
			g_focusWidget->paintEvent();
			swapBuffers( (gbm_surface*)g_focusWidget->nativeWindow() );
		}
	}
}

void switchVt( int t )
{
	int fd = open("/dev/tty1", O_RDONLY);
	ioctl(fd, VT_ACTIVATE, t);
}

void listFont()
{
	FcFontSet *fs;
	fs = FcConfigGetFonts(NULL, FcSetSystem);

	for( int i=0; i<fs->nfont; i++)
	{
		FcPattern *pat;
		pat = fs->fonts[i];
		FcChar8 *fam, *sty;
		FcResult ret = FcPatternGetString(pat, FC_FAMILY, 0, &fam);
		FcResult ret2 = FcPatternGetString(pat, FC_FAMILYLANG, 0, &sty);

		if(ret== FcResultMatch && ret2 == FcResultMatch) {
			printf("%s, %s\n", fam, sty);
		}
	}
}

void initCursor()
{
	int ret;
	int is_cursor, is_scanout;

	drm_mode_create_dumb create_arg;
	drm_mode_destroy_dumb destroy_arg;
	create_arg.bpp = 32;
	create_arg.width = 64;
	create_arg.height = 64;

	ret = drmIoctl(g_hDevice, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg);

	drm_mode_map_dumb map_arg;
	memset(&map_arg, 0, sizeof(map_arg));

	map_arg.handle = create_arg.handle;
	ret = drmIoctl(g_hDevice, DRM_IOCTL_MODE_MAP_DUMB, &map_arg);

	char* cursorImg = (char*)mmap(0, create_arg.size, PROT_WRITE, MAP_SHARED, g_hDevice, map_arg.offset);
	if (cursorImg == MAP_FAILED) {
		return;
	}

	size_t szCursorImage = 16384;

	for(size_t i = 0; i<64; i++) {
		for(size_t j = 0; j<64; j++) {
			size_t idx = i*64*4+j*4;
			int c = (i==0||j==0||i>19||j>14) ? 0x0 : 0xff;
			int al = (i>20||j>15) ? 0x0 : 0xff;

			cursorImg[idx] = c;
			cursorImg[idx+1] = c;
			cursorImg[idx+2] = c;
			cursorImg[idx+3] = al;
		}
	}

	g_szCursorClip[0] = g_stDisplayMode.hdisplay; g_szCursorClip[1] = g_stDisplayMode.vdisplay;
	drmModeSetCursor( g_hDevice, g_hCrtcId, create_arg.handle, 64, 64);
	drmModeMoveCursor(g_hDevice, g_hCrtcId, g_iMousePos[0], g_iMousePos[1]);
}

int main(int argc, char *argv[])
{ 
	listFont();
	switchVt(8);

	initKms();
	initInputDevice();

	int width = g_stDisplayMode.hdisplay, height = g_stDisplayMode.vdisplay;
	gbm_device *gbm;
	gbm = gbm_create_device(g_hDevice);

	gbm_surface *gs;
	gs = gbm_surface_create(gbm, width, height,  GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING );

	initCursor();

	GuiDemo *demoTexture = new GuiDemo( gbm, gs );

	setFocus(demoTexture);
	
	mainloop();

	delete demoTexture;
	gbm_surface_destroy(gs);
	gbm_device_destroy(gbm);
	close(g_hDevice);	
	switchVt(1);
	return 0;
}
